package com.mybatis;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.sql.*;
import java.util.*;

/**
 * mapper代理工厂
 */
public class MapperProxyFactory {

    /**
     * sql参数注入和列查询结果获取处理器map集合
     */
    private static Map<Class, TypeHandler> typeHandlerMap = new HashMap<>();


    static {
        //注册sql参数注入处理器
        typeHandlerMap.put(String.class, new StringTypeHandle());
        typeHandlerMap.put(Integer.class, new IntegerTypeHandle());
        //加载数据库驱动
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 生成接口的代理对象
     * @param mapper
     * @return
     * @param <T>
     */
    public static <T> T getMapper(Class mapper){
        Object proxyInstance = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{mapper}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //代理逻辑
                //解析类信息，生成数据库连接，执行sql，返回sql执行结果

                //1.获取数据库连接
                Connection connection = getConnection();

                //获取当前执行方法的Select注解，得到要执行的sql
                Select select = method.getAnnotation(Select.class);
                String sql = select.value();//select * from user where name=#{name} and age=#{age}

                //获取方法参数名，通过解析参数的Param注解获取参数名，得到参数名和参数值的映射关系
                Map<String, Object> parameterMap = new LinkedHashMap<>(); //参数名和参数值映射关系
                Annotation[][] parameterAnnotations = method.getParameterAnnotations();
                for (int i = 0; i < parameterAnnotations.length; i++) {
                    for (Annotation annotation : parameterAnnotations[i]) {
                        if (annotation.annotationType() == Param.class){
                            Param param = (Param) annotation;
                            String paramName = param.value();
                            parameterMap.put(paramName, args[i]);
                        }
                    }
                }

                //解析sql，生成预编译sql ,记录sql参数注入的位置
                ParameterMappingTokenHandler tokenHandler = new ParameterMappingTokenHandler();
                GenericTokenParser tokenParser = new GenericTokenParser("#{", "}", tokenHandler);
                //得到预编译sql select * from user where name=? and age=?
                sql = tokenParser.parse(sql);

                //2.构造PreparedStatement , 进行sql预编译
                PreparedStatement prepareStatement = connection.prepareStatement(sql);
                //获取参数和对应位置映射关系
                List<ParameterMapping> parameterMappings = tokenHandler.getParameterMappings();
                //根据参数名和参数位置将参数注入sql
                for (int i = 0; i < parameterMappings.size(); i++) {
                    //参数名
                    String parameterName = parameterMappings.get(i).getProperty();
                    //参数值
                    Object value = parameterMap.get(parameterName);
                    //获取参数类型
                    Class<?> parameterType = value.getClass();
                    //根据参数类型进行赋值
                    TypeHandler typeHandler = typeHandlerMap.get(parameterType);
                    typeHandler.setParameter(prepareStatement, i+1, value);
                }

                //3.执行sql
                prepareStatement.execute();

                //4.获取执行结果
                ResultSet resultSet = prepareStatement.getResultSet();
                //最终解析返回的结果
                Object result = null;
                List<Object> resultList = new ArrayList<>();
                //获取方法的返回值
                Class resultType = null;
                Type genericReturnType = method.getGenericReturnType();
                if (genericReturnType instanceof Class){
                    //不是泛型
                    resultType = (Class) genericReturnType;
                }else if (genericReturnType instanceof ParameterizedType){
                    //是泛型
                    ParameterizedType type = (ParameterizedType) genericReturnType;
                    //真正的泛型类型
                    Type[] actualTypeArguments = type.getActualTypeArguments();
                    resultType = (Class) actualTypeArguments[0];
                }

                //建立类的字段名和对应的set方法映射关系
                Map<String, Method> setterMethodMapping = new HashMap<>();
                for (Method declaredMethod : resultType.getDeclaredMethods()) {
                    if (declaredMethod.getName().startsWith("set")){
                        String propertyName = declaredMethod.getName().substring(3);
                        //首字母小写后获得参数名
                        propertyName = propertyName.substring(0,1).toLowerCase(Locale.ROOT) + propertyName.substring(1);
                        setterMethodMapping.put(propertyName, declaredMethod);
                    }
                }

                //获取结果集查询出来的字段列表
                ResultSetMetaData metaData = resultSet.getMetaData();
                List<String> columList = new ArrayList<>();
                for (int i = 0; i < metaData.getColumnCount(); i++) {
                    columList.add(metaData.getColumnName(i+1));
                }

                //创建该类的实例并给该实例对象赋值
                while (resultSet.next()) {
                    Object instance = resultType.newInstance();
                    for (String colum : columList) {
                        //获取类该字段对应的set方法
                        Method setterMethod = setterMethodMapping.get(colum);
                        //获取类该字段对应的类型
                        Class<?> fieldType = setterMethod.getParameterTypes()[0];//setter方法参数只有一个
                        //根据字段类型获取结果处理器
                        TypeHandler typeHandler = typeHandlerMap.get(fieldType);
                        Object fieldResult = typeHandler.getResult(resultSet, colum);
                        //执行set方法给字段赋值
                        setterMethod.invoke(instance, fieldResult);
                    }
                    //放入list中
                    resultList.add(instance);
                }

                if (method.getReturnType().equals(List.class)){
                    result = resultList;
                }else {
                    result = resultList.get(0);
                }

                //关闭数据库连接
                connection.close();
                return result;
            }
        });
        return (T) proxyInstance;
    }

    private static Connection getConnection() throws SQLException {
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8",
                "root", "root");
        return connection;
    }
}
